package gameEngine.agents;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import ui.flowManager.expirimentProperties.AdditionalSettings;
import exportSystem.Result.MetricResult;
import exportSystem.Result.Result;
import gameEngine.agents.choosingStrategy.AdaptiveStrategy;
import gameEngine.agents.choosingStrategy.ChoosingStrategy;
import gameEngine.agents.choosingStrategy.representation.StrategyRepresentation;
import gameEngine.agents.distrebution.representation.DistributionRepresentation;
import gameEngine.agents.observationSystem.analyzer.EnumOfAnalyzer.Analyzer_Type;
import gameEngine.agents.observationSystem.samples.Sample;
import gameEngine.agents.teamMateModel.AgentMock;
import gameEngine.agents.teamMateModel.TeamMateModel;
import gameEngine.agents.teamMateModel.EnumOfTeamateModel.Teammate_Meodel_Type;
import gameEngine.game.TurnBasedScoredGame;
/**
 * Adaptive agent extend the basic one and offer the following:
 * 1. stop the game if for 3 turns no major change was done to his {@link TeamMateModel}
 * 2. holds a {@link Result} for output
 * 3. holds an adaptive strategy 
 * @author alon
 *
 */
public class AdaptiveAgent extends BasicAgent {

	private Result result;
	public boolean isStopping;
	public boolean isLogging;
	private HashMap<Integer, Integer> stopArray; 
	private final int stopVar=3;
	
	/**
	 * Constructor for simple parameters 
	 * @param iD
	 * @param epsilonGreedy
	 * @param competence
	 */
	public AdaptiveAgent(int iD, double epsilonGreedy, double competence) {
		super(iD, epsilonGreedy, competence);
	}
	/**
	 * @see BasicAgent.init
	 */
	public void init(Analyzer_Type analayzerType,StrategyRepresentation strRepresentation,
			Teammate_Meodel_Type teamMateType,
			DistributionRepresentation diRepresentation ,ArrayList<Agent> agents,AdditionalSettings settings )
	{
		super.init(analayzerType,strRepresentation ,teamMateType, diRepresentation ,agents);
		result=new MetricResult(this.getTeamMateModel().getTeamMates().size());
		this.isStopping=settings.isStopAutomatically();
		this.isLogging=settings.isLog();		
		initStopArray();
	}
	
	/**
	 * this method is used to ass ChoosingStrategy right after init()
	 * @param strategy the ChoosingStrategy to be add
	 * @param turnNumber integer telling us when to switch to this strategy
	 */
	public void addStrategy(ChoosingStrategy strategy,int turnNumber){
		//we know that we can cast strategy to adaptive since this is an adaptive agent
		((AdaptiveStrategy)this.getChoosingStrategy()).addStrategy(strategy, turnNumber);
	}
	
	/**
	 * sets the game global flag to false-> stop the game
	 */
	public void stopGame(){
		TurnBasedScoredGame.RUNNIG_FLAG=false;
	}
	
	/**
	 * do the following:
	 * #1.update turn in strategy as we use it to keep track of the current turn
	 * #2.save current passing agent mock state copy (before analyze update it state)
	 * #3.analyze sample
	 * #4.export metric to Result
	 * #5.if is stopping set to true compare newly analyzed mock state
	 *    to old copy  
	 */
	public void analyzeSample(Sample s) {
		//#1
		((AdaptiveStrategy)this.getChoosingStrategy()).updateTurn();
		//#2
		AgentMock before=this.getTeamMateModel().getAgentById(s.getID()).clone();
		//#3
		super.analyzeSample(s);
		//#4
		export();
		//#5
		if(isStopping){
			//AnalyzeTeamModel
			AgentMock after=this.getTeamMateModel().getAgentById(s.getID());
			//if true agent passed, add 1 to it's stopping counter
			//we want stopVar continues hits for each agent at list to stop
			if(compareAgent(before,after)){
				stopArray.put(s.getID(),(stopArray.get(s.getID()))+1);
			}else{
				stopArray.put(s.getID(),0);
			}
			for(Integer key:stopArray.keySet()){
				if(stopArray.get(key)<stopVar){
					return;
				}
			}
			//all passed, stop the game
			stopGame();
		}
	}
	
	
	/**
	 * adds a a result metric (row) at each turns end, right after analyzing the turn
	 */
	public void export(){
		ArrayList<Double> metric=new ArrayList<Double>();
		Iterator<AgentMock> it= this.getTeamMateModel().getTeamMates().iterator();
		while(it.hasNext()){
			AgentMock mock=it.next();
			metric.add(mock.getAgentCompitance());
		}
		Iterator<AgentMock> it2= this.getTeamMateModel().getTeamMates().iterator();
		while(it2.hasNext()){
			AgentMock mock=it2.next();
			metric.add(mock.getAgentEGreedy());
		}
		result.addMetric(metric);
	}
	
	/**
	 * this method check if there was a significant change to competence and epsilon 
	 * @param before {@link AgentMock} before turn analyze
	 * @param after {@link AgentMock} after turn analyze
	 * @return
	 */
	private boolean compareAgent(AgentMock before,AgentMock after){
		double comp1=before.getAgentCompitance();
		double eps1=before.getAgentEGreedy();
		double comp2=after.getAgentCompitance();
		double eps2=after.getAgentEGreedy();
		boolean flagComp=false;
		boolean flagEps=false;
		if((Math.abs(comp1-(Math.abs(comp1-comp2)))/comp1)>0.97){
			flagComp=true;
		}
		if((Math.abs(eps1-(Math.abs(eps1-eps2)))/eps1)>0.95){
			flagEps=true;
		}
		return flagComp&&flagEps;
	}
	
	/**
	 * used to access the result stored on the agent
	 * @return Result result
	 */
	public Result getResult(){
		return result;
	}
	
	public void setResult(Result r){
		this.result=r;
	}
	
	private void initStopArray(){
		stopArray=new HashMap<Integer, Integer>();
		for(AgentMock a:this.getTeamMateModel().getTeamMates()){
			if(a.getId()==this.getID()){
				stopArray.put(a.getId(), stopVar);
			}else{
				stopArray.put(a.getId(), 0);
			}
		}
	}
	
	public HashMap<Integer,Integer> getStopArray() {
		return stopArray;
	}
	public void setStopArray(HashMap<Integer,Integer> stopArray) {
		this.stopArray = stopArray;
	}
	
	
	
	public AdaptiveAgent clone(){
		AdaptiveAgent clone=new AdaptiveAgent(getID(), getEpsilonGreedy(), getCompetence());
		clone.setTeamMateModel(getTeamMateModel().clone());
		clone.setObservationAnalyzer(getObservationAnalyzer().clone());
		clone.setChoosingStrategy(getChoosingStrategy().clone());
		clone.setDistribution(getDistribution().clone());
		clone.isLogging=isLogging;
		clone.isStopping=isStopping;		
		return clone;
		
	}
	
	/**
	 * to be used only on an Agent clone!!!!
	 * we need to set the new model after cloning all agents
	 * as some might have different properties values 
	 * @param agents the new cloned list
	 */
	public void initClonesModel(ArrayList<Agent> agents){
		super.initClonesModel(agents);
		this.setResult(new MetricResult(getTeamMateModel().getTeamMates().size()));
		initStopArray();
	}
	
}
